package com.terminal;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Properties;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
//import android.text.method.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.Window;
//import android.view.Menu.Item;
import android.widget.TextView;

public class TerminalActivity extends Activity {
	TerminalSocket st = null;
	TextView txt1 = null;
	public String Server = "ftp.elisoft.co.kr"; 
	public int port = 22;
	public int font = 12;
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.terminalactivity);
	
		this.getWindow().setType(Window.FEATURE_NO_TITLE);
		txt1 = (TextView) findViewById(R.id.txt1);
		txt1.setText(Server);

		final TerminalView termView = (TerminalView) findViewById(R.id.terminalview);
		termView.fontSize = font;
		termView.setVisibility(View.VISIBLE);
		final MyHandler hnd = new MyHandler(termView, txt1);
		st = new TerminalSocket(hnd);
		st.Server = Server;
		st.Port = port;
		termView.ts = st;

	}
	

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        menu.add(0, 1, 0, "ESC");
        SubMenu sm = menu.addSubMenu(0, 2, 0, "Fx-keys");
        sm.add(0, 11, 0, "F1");
        sm.add(0, 12, 0, "F2");
        sm.add(0, 13, 0, "F3");
        sm.add(0, 14, 0, "F4");
        sm.add(0, 15, 0, "F5");
        sm.add(0, 16, 0, "F6");
        sm.add(0, 17, 0, "F7");
        sm.add(0, 18, 0, "F8");
        sm.add(0, 19, 0, "F9");
        sm.add(0, 20, 0, "F10");
        return true;
    }
    
    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        super.onMenuItemSelected(featureId, item);
		byte [] buf = null;
        switch(item.getItemId()) {
        case 1:
			buf = new byte[] {0x1b};
            break;
        case 11:
			buf = new byte[] { 0x1b, 0x31 };
            break;
        case 12:
			buf = new byte[] { 0x1b, 0x32 };
            break;
        case 13:
			buf = new byte[] { 0x1b, 0x33 };
            break;
        case 14:
			buf = new byte[] { 0x1b, 0x34 };
            break;
        case 15:
			buf = new byte[] { 0x1b, 0x35 };
            break;
        case 16:
			buf = new byte[] { 0x1b, 0x36 };
            break;
        case 17:
			buf = new byte[] { 0x1b, 0x37 };
            break;
        case 18:
			buf = new byte[] { 0x1b, 0x38 };
            break;
        case 19:
			buf = new byte[] { 0x1b, 0x39 };
            break;
        case 20:
			buf = new byte[] { 0x1b, 0x30 };
            break;
            
        }
        if(buf != null)
        	st.SpecialSocketSend(buf);
       
        return true;
    }

	public boolean onKeyDown(int KeyCode, KeyEvent event) {
		byte[] buf = null;

		switch (KeyCode) {
		case android.view.KeyEvent.KEYCODE_DPAD_UP:
			buf = new byte[] { 0x1b, 0x4f, 0x41 };
			break;
		case android.view.KeyEvent.KEYCODE_DPAD_DOWN:
			buf = new byte[] { 0x1b, 0x4f, 0x42 };
			break;
		case android.view.KeyEvent.KEYCODE_DPAD_RIGHT:
			buf = new byte[] { 0x1b, 0x4f, 0x43 };
			break;
		case android.view.KeyEvent.KEYCODE_DPAD_LEFT:
			buf = new byte[] { 0x1b, 0x4f, 0x44 };
			break;
		case android.view.KeyEvent.ACTION_UP:
			break;
		case android.view.KeyEvent.KEYCODE_DPAD_CENTER: 
			buf = new byte [] {10};
			break;
		case android.view.KeyEvent.KEYCODE_DEL:
			buf = new byte [] {0x08};
			break;		
		default:
			android.view.KeyCharacterMap km = android.view.KeyCharacterMap.load(event.getDeviceId());
			int k = km.get(KeyCode, event.getMetaState());
			buf = new byte[] { (byte) k };
		}

		if(buf != null)
			st.SpecialSocketSend(buf);

		return super.onKeyDown(KeyCode, event);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		try{
			st.sk.close();
		}
		catch(Exception ex){}
		
	}
	
}

class MyHandler extends Handler {
	TerminalView tv;
	TextView txt;

	MyHandler(TerminalView termView, TextView txt) {
		super();
		tv = termView;
		this.txt = txt;
	}

	@SuppressWarnings("unchecked")
	public void handleMessage(android.os.Message msg) {
	
		//HashMap<String, String> hm = (HashMap<String, String>) msg.getData();
		Serializable serialized = msg.getData().getSerializable("hashmap");
		HashMap<String, String> hm = (HashMap<String, String>) serialized;
		
		String msgText = hm.get("message");
		if(msg.what == 0)
		{
			tv.cp.AddBuffer(msgText.toCharArray());
			tv.invalidate(0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
			txt.setVisibility(View.INVISIBLE);
		}
		else if(msg.what == 1)
		{
			txt.setText(msgText);
			txt.setTextColor(0xFFFF0000);
			txt.setVisibility(View.VISIBLE);			
		}
		else if(msg.what == 2)
		{
			txt.setText(msgText);
			txt.setTextColor(0xFF00FF00);
			txt.setVisibility(View.VISIBLE);			
		}
	}

}


class TerminalSocket implements Runnable {
	public Thread tr;
	private MyHandler hnd;
	protected Socket sk = null;
	private InputStream iStream = null;
	public OutputStream oStream = null;
	public byte X = 1;
	public byte Y = 1;
	protected String Server = "";
	protected int Port = 0;

	public TerminalSocket(MyHandler h) {
		hnd = h;
		tr = new Thread(this);
	}
	

	public void SocketStart() {
		tr.start();
	}

	private Message StringToMessage(String s) {
	
		Message msg = new Message();
	    msg.what = 0;
	    HashMap<String, String> hm = new HashMap<String, String>();
	    hm.put("message", s);
	    Bundle bundle = new Bundle();
	    bundle.putSerializable("hashmap", hm);
	    msg.setData(bundle);
//		HashMap<String, String> hm = new HashMap<String, String>();
//		hm.put("message", s);
//		msg.setData(hm);
		return msg;
	}
	
	private Message ErrorToMessage(String s) {
		Message msg = new Message();
		msg.what = 1;
		HashMap<String, String> hm = new HashMap<String, String>();
	    hm.put("message", s);
	    Bundle bundle = new Bundle();
	    bundle.putSerializable("hashmap", hm);
	    msg.setData(bundle);
//		HashMap<String, String> hm = new HashMap<String, String>();
//		hm.put("message", s);
//		msg.setData(hm);
		return msg;
	}
	
	private Message InfoToMessage(String s) {
		Message msg = new Message();
		msg.what = 2;
		HashMap<String, String> hm = new HashMap<String, String>();
	    hm.put("message", s);
	    Bundle bundle = new Bundle();
	    bundle.putSerializable("hashmap", hm);
	    msg.setData(bundle);
//		HashMap<String, String> hm = new HashMap<String, String>();
//		hm.put("message", s);
//		msg.setData(hm);
		return msg;
	}

	public void run() {
		InetAddress ia = null;
		hnd.sendMessage(InfoToMessage("Resolve server name " + Server));
		try {
			ia = InetAddress.getByName(Server);
		} catch (UnknownHostException ex) {
			hnd.sendMessage(ErrorToMessage("Unknown host " + Server));
			return;
		}
		hnd.sendMessage(InfoToMessage("Connect to "  + Server + ":" + Port));
		try {
			sk = new java.net.Socket(ia, Port);
		} catch (IOException ex) {
			hnd.sendMessage(ErrorToMessage("Socket time out for " + Server + ":" + Port));
			return;
		}

		try {
			iStream = sk.getInputStream();
			oStream = sk.getOutputStream();
		} catch (IOException ex) {
			hnd.sendMessage(ErrorToMessage("Protocol error for " + Server + ":" + Port));
			return;
		}


		SpecialSocketSend(new byte[] { (byte) 0xff, (byte) 0xfb, (byte) 0x1f });
		while (true) {
			byte[] buf = new byte[2048];
			try {
				int i = iStream.read(buf);
				Parsing(buf, i);
			} catch (Exception ex) {
				hnd.sendMessage(ErrorToMessage("Connection closed read"));
			}
		}
	}

	private byte mpState = TerminalConstants.TS_MAIN;
	private byte tc = 0;
	private boolean isFirstByte = true;
	private byte bSub = 0;
	private byte bSubValue = 0;

	private void Parsing(byte[] bData, int count) {
		for (int i = 0; i < count; i++) {
			byte curByte = bData[i];
			switch (mpState) {
			case TerminalConstants.TS_MAIN: {
				if (curByte == TerminalConstants.CMD_IAC) {
					mpState = TerminalConstants.TS_IAC;
				} else {
					String s = new String(bData, i, count - i);
					hnd.sendMessage(StringToMessage(s));
					return;
				}
				break;
			}
			case TerminalConstants.TS_IAC: {
				CommandParser(curByte);
				break;
			}
			case TerminalConstants.TS_OPTIONS: {
				OptionsParses(curByte);
				break;
			}
			case TerminalConstants.TS_SUB: {
				SubParser(curByte);
				break;
			}

			}
		}
	}

	private void OptionsParses(byte opt) {

		if (opt == TerminalConstants.OP_SGA) {
			if (tc == TerminalConstants.CMD_DO) {
				try {
					oStream.write(new byte[] { TerminalConstants.CMD_IAC,
							TerminalConstants.CMD_WILL, opt });
				} catch (Exception ex) {
				}
				mpState = TerminalConstants.TS_MAIN;
				return;
			}
			if (tc == TerminalConstants.CMD_WILL) {
				try {
					oStream.write(new byte[] { TerminalConstants.CMD_IAC,
							TerminalConstants.CMD_DO, opt });
				} catch (Exception ex) {
				}
				mpState = TerminalConstants.TS_MAIN;
				return;
			}
		}
		if (opt == TerminalConstants.OP_ECHO) {
			if (tc == TerminalConstants.CMD_DO) {
				SpecialSocketSend(new byte[] { TerminalConstants.CMD_IAC,
						TerminalConstants.CMD_WILL, opt });
				mpState = TerminalConstants.TS_MAIN;
				return;
			}
			if (tc == TerminalConstants.CMD_WILL) {
				SpecialSocketSend(new byte[] { TerminalConstants.CMD_IAC,
						TerminalConstants.CMD_DO, opt });
				mpState = TerminalConstants.TS_MAIN;
				return;
			}
		}

		if (tc == TerminalConstants.CMD_DO) {
			if (opt == TerminalConstants.OP_TYPE) {
				SpecialSocketSend(new byte[] { TerminalConstants.CMD_IAC,
						TerminalConstants.CMD_WILL, opt });
				mpState = TerminalConstants.TS_MAIN;
				return;
			}
			if (opt == TerminalConstants.OP_NAWS) {
				SpecialSocketSend(new byte[] { (byte) 0xff, (byte) 0xfa,
						(byte) 0x1f, (byte) 0x00, X, (byte) 0x00, Y,
						(byte) 0xff, (byte) 0xf0 });
				mpState = TerminalConstants.TS_MAIN;
				return;
			}
		}
		if (tc == TerminalConstants.CMD_DO || tc == TerminalConstants.CMD_DONT)
			SpecialSocketSend(new byte[] { TerminalConstants.CMD_IAC,
					TerminalConstants.CMD_WONT, opt });
		else
			SpecialSocketSend(new byte[] { TerminalConstants.CMD_IAC,
					TerminalConstants.CMD_DONT, (byte) opt });

		mpState = TerminalConstants.TS_MAIN;
	}

	protected void SpecialSocketSend(byte[] buff) {
		try {
			oStream.write(buff);
			oStream.flush();
		} catch (Exception ex) {
			hnd.sendMessage(ErrorToMessage("Connection closed write"));
		}
	}

	private void CommandParser(byte cmd) {
		switch (cmd) {
		case TerminalConstants.CMD_DO:
		case TerminalConstants.CMD_DONT:
		case TerminalConstants.CMD_WILL:
		case TerminalConstants.CMD_WONT:
			tc = cmd;
			mpState = TerminalConstants.TS_OPTIONS;
			break;
		case TerminalConstants.CMD_SB:
			mpState = TerminalConstants.TS_SUB;
			break;
		case TerminalConstants.CMD_SE:
			ProcessSE();
			break;
		case TerminalConstants.CMD_IAC:
			mpState = TerminalConstants.TS_MAIN;
			break;
		default:
			mpState = TerminalConstants.TS_MAIN;
			break;
		}
	}

	private void ProcessSE() {
		if (bSub == 24) {
			try {
				oStream.write(new byte[] { (byte) 0xff, (byte) 0xfa, 0x18,
						0x00, 0x56, 0x54, 0x31, 0x30, 0x30, (byte) 0xff,
						(byte) 0xf0 });
			} catch (Exception ex) {
			}

		}
		mpState = TerminalConstants.TS_MAIN;
	}

	private void SubParser(byte opt) {
		if (opt == TerminalConstants.CMD_IAC) {
			mpState = TerminalConstants.TS_IAC;
			return;
		}
		if (isFirstByte) {
			bSub = opt;
			isFirstByte = false;
		} else {
			bSubValue = opt;
			isFirstByte = true;
		}
	}
}
